#include "DXEngine.h"
#include "globals.h"
#include "templatevector.h"

#ifdef _DEBUG
	#ifndef HR

	#define HR(x)					\
		{							\
		HRESULT hr = x;				\
		if (FAILED(hr))				\
			{						\
			DXTRACE_ERR(#x, hr);	\
			DebugBreak();			\
			}						\
		}
	#endif
#else
	#ifndef HR
		#define HR(x) x;
	#endif
#endif

DXEngine::DXEngine()
	: m_pD3DDevice(NULL)
	, m_pSpriteManager(NULL)
	, m_pFontManager(NULL)
	, ObjPos(NULL)
	, ObjTex(NULL)
{}

bool DXEngine::Init(HWND hWnd, int scrHeight, int scrWidth)
{
	/* if at any point a function fails, return false */
	if(!InitDevice(hWnd, scrHeight, scrWidth))
		return false;
	if(!InitManagers())
		return false;

	return true;
}

/**
 * Sets up the main IDirect3DDevice9 object
 * Only to be used within Graphics' initialization
 *
 * @param hWnd is passed so that the device can connect to the proper window
 * @return whether or not the device was setup successfully
 */
bool DXEngine::InitDevice(HWND hWnd, int scrHeight, int scrWidth)
{
	/* Create a temporary IDirect3D9 object used to setup the device */
	IDirect3D9* pD3DObject = NULL;
	pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);

	if(NULL == hWnd)
		return false;

	/* Setup a Rect with the window dimensions to make sure they have been set */
	RECT rClientRect;
	if(!::GetClientRect(hWnd, &rClientRect))
		throw "GetClientRect() failed";
	//dont really need this
	scrHeight = (int)(rClientRect.bottom - rClientRect.top);
	scrWidth = (int)(rClientRect.right - rClientRect.left);

	D3DCAPS9 D3DCaps;
	HR(pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));


	// determine what type of vertex processing to use
	DWORD dwBehaviorFlags = 0;

	if(D3DCaps.VertexProcessingCaps != 0)
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;	

	//set up present parameters
	memset(&m_D3Dpp, 0, sizeof(m_D3Dpp));
	m_D3Dpp.hDeviceWindow			= hWnd;
	m_D3Dpp.Windowed				= true;				// SET TRUE FOR WINDOWE MODE
	m_D3Dpp.AutoDepthStencilFormat	= D3DFMT_D24S8;
	m_D3Dpp.EnableAutoDepthStencil	= TRUE;
	m_D3Dpp.BackBufferCount			= 1;
	m_D3Dpp.BackBufferFormat		= D3DFMT_X8R8G8B8;
	m_D3Dpp.BackBufferWidth			= scrWidth;
	m_D3Dpp.BackBufferHeight		= scrHeight;
	m_D3Dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_D3Dpp.PresentationInterval	= D3DPRESENT_INTERVAL_DEFAULT;
	m_D3Dpp.Flags					= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	m_D3Dpp.MultiSampleQuality		= 0;
	m_D3Dpp.MultiSampleType			= D3DMULTISAMPLE_NONE;

	// setup the d3ddevice using the d3dobject and present parameters
	HRESULT hr = pD3DObject->CreateDevice(D3DADAPTER_DEFAULT, 
										  D3DDEVTYPE_HAL, 
										  hWnd,
										  dwBehaviorFlags, 
									      &m_D3Dpp,
										  &m_pD3DDevice);
	pD3DObject->Release();
	pD3DObject = NULL;
	switch(hr)
	{
	case D3DERR_DEVICELOST:
		g_console->Print("DirectX device was lost");break;
	case D3DERR_INVALIDCALL:
		g_console->Print("DirectX device had an invalid call in init");break;
	case D3DERR_NOTAVAILABLE:
		g_console->Print("DirectX device init was not available");break;
	case D3DERR_OUTOFVIDEOMEMORY:
		g_console->Print("DirectX device could not init - out of memory");break;
	}

	assert(hr == D3D_OK && "The device is broken");
	if(hr != D3D_OK)
		return false;

	return true;
}

/**
 * This simply sets of the sprite and font managers, calling their respective init functions
 * Only to be used within Graphics' initialization
 * 
 * @return whether or not either of these intialization processes failed
 */
bool DXEngine::InitManagers()
{
	HRESULT hr = D3DXCreateFont(m_pD3DDevice, 18, 0, NULL, 0, false,
				   DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
				   DEFAULT_PITCH | FF_DONTCARE, TEXT("Times New Roman"), &m_pFontManager);

	hr = D3DXCreateSprite(m_pD3DDevice, &m_pSpriteManager);

	if(hr != D3D_OK)
		return false;
	return true;
}

/**
 * This allows for assets to be added manually to the asset library
 *
 * @param texLoc - location of the file
 * @return - an index of the asset that's been added to the library
 */
int DXEngine::AddAsset(char* texLoc, color cKey)
{
	// TODO: Check to see if the asset has been added before attempting to add it again
	LPDIRECT3DTEXTURE9 pTexture;
	
	// determine whether or not the image has a color key, then set it
	D3DCOLOR colorKey;
	if(!cKey.red && !cKey.green && !cKey.blue)
		colorKey = NULL;
	else
		colorKey = D3DCOLOR_ARGB(255, cKey.red, cKey.green, cKey.blue);

	// use this function instead, it allows for more control in how it is setup - Michael
	D3DXCreateTextureFromFileEx(m_pD3DDevice,								// the device
								texLoc,										// location of the file
								D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, // prevents rounding to even binary numbers on dimensions
								0,											// default mip mapping
								0,											// use non-dynamic texturing
								D3DFMT_UNKNOWN,								// pixel formatting - takes directly from file
								D3DPOOL_MANAGED,							// manage the memory class for us
								D3DX_DEFAULT,								// how the image is filtered (normal)
								D3DX_DEFAULT,								// how the image is filtered (mip-filtering)
								colorKey,									// don't use a color key
								NULL, NULL,									// we don't need image_info or palette, so put NULL for both
								&pTexture);									// the created texture

	Asset<LPDIRECT3DTEXTURE9> A(texLoc,pTexture);
	return m_D3DXTextures.AddAsset(A);
}

/**
 * Asks DirectX for the dimensions of a loaded texture
 * 
 * @param index - is the index in the asset library
 * @param return - the width and height of the asset
 */
point DXEngine::GetAssetDimensions(int index)
{
	point dimensions;
	LPDIRECT3DTEXTURE9 tex;
	D3DSURFACE_DESC desc;
	tex = m_D3DXTextures.GetAssetData(index);
	tex->GetLevelDesc(0, &desc);

	dimensions.x = (float)desc.Width;
	dimensions.y = (float)desc.Height;

	return dimensions;
}


/**
 * Removes the asset from the asset library
 *
 * @param index - marks the index of the texture to be removed
 * @return whether or not the removal process was successful
 *
 */
bool DXEngine::RemoveAsset(int index)
{
	// TODO: Make RemoveByFile return whether or not it was successful
	m_D3DXTextures.RemovebyIndex(index);
	return true;
}

/**
 * Responsible for drawing a single object to the screen
 * 
 * @param pObj is the object to be drawn
 */
void DXEngine::Draw(BaseObject* pObj)
{
	// don't even bother with this function if the object is not active
	if(!pObj->isActive())
		return;

	ObjPos = new D3DXVECTOR3((float)pObj->getLoc().x,(float)pObj->getLoc().y,0.0f);

	//added scaling and and rotating 
	D3DXMatrixScaling(&scaleMat, (float)pObj->getScale().x,(float)pObj->getScale().y,0.0f);			// Scaling

	//had to convert vector to matrix
	D3DXMatrixTranslation(&transMat, ObjPos->x, ObjPos->y, ObjPos->z);			// Translating

	//have to remember that you have to call D3DXToRadian
	//x is the only one we are going to use which is Roll
	D3DXMatrixRotationX(&rotMat, D3DXToRadian(pObj->getRot().x));

	//D3DXMatrixRotationYawPitchRoll(&rotMat,   D3DXToRadian(pObj->getRot().y), D3DXToRadian(pObj->getRot().y), D3DXToRadian(pObj->getRot().x)); // Rotation on Yaw, Pitch, and Roll

	ObjTex = m_D3DXTextures.GetAssetData(pObj->GetAssetIndex());

	//sets the transforms
	m_pSpriteManager->SetTransform(&(scaleMat*rotMat*transMat));
	//pass in NULL because all the Transfroms are done up above	
	////Set to 0xFFFFFFFF so that it appears as normal
	m_pSpriteManager->Draw(ObjTex, 0 , NULL,NULL, 0xFFFFFFFF);
}
#include <sstream>
void DXEngine::Draw(char* C,int x,int y,int h,int w) 
{
	RECT R;
	R.left = x;R.right = w;R.top = y;R.bottom = h;
	m_pFontManager->DrawTextA(NULL,
							  C,//s.str().c_str(),
							  strlen(C),//s.str().size(),
							  &R,DT_LEFT | DT_NOCLIP,
							  D3DCOLOR_ARGB(255, 255, 255, 255));
}
//void DXEngine::BeginScene() {
//}
//void DXEngine::BeginSprite() {
//}
void DXEngine::Render(IndexedLinkedList * GameObjects, IndexedLinkedList * MenuObjects)
{
	// clear the screen
	HR(m_pD3DDevice->Clear(NULL, NULL, 
						D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
						D3DCOLOR_XRGB(0, 0, 0), 
						1.0f, 0));

	// tell the device that we're going to start preparing the frame
	m_pD3DDevice->BeginScene();

	// tell the sprite manager to wake up and get ready
	HR(m_pSpriteManager->Begin(D3DXSPRITE_ALPHABLEND));
	
	static Record * cursor;
	cursor = GameObjects->Get(0);
	/* Draw all game objects */
	for(int i = 0;i < GameObjects->GetSize();i++) {
		cursor = GameObjects->Get(i);
		Draw((BaseObject*)cursor->Data);
	}

	
	/* Draw all gui objects */
	for(int i = 0;i < MenuObjects->GetSize();i++) {
		cursor = MenuObjects->Get(i);
		Draw((BaseObject*)cursor->Data);
		for(int i = 0;strlen(((gui_morph*)cursor->Data)->access(i).val);i++) {
			if( m_AllMenuText.indexOf(((gui_morph*)cursor->Data)->access(i)) == -1)
				m_AllMenuText.add(((gui_morph*)cursor->Data)->access(i));
		}
	}
	//strlen(((gui_morph*)cursor->Data)->access(i).val)
	//m_AllMenuText.add(((gui_morph*)cursor->Data)->access(0));

	

	// tell the sprite manager that we're done with it
	HR(m_pSpriteManager->End());

	/* Draw all textboxes */
	static TextBox T;
	for(int i = 0;i < m_AllMenuText.size();i++) {
		T = m_AllMenuText.get(i);
		Draw(T.val,T.pos.x,T.pos.y,T.pos.height,T.pos.width);
	}

	// mark that we've finished preparing and st.end completed frame to the window
	m_pD3DDevice->EndScene();
	m_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}

//void DXEngine::RenderMenu(IndexedLinkedList * AllObjects)
//{
//}
//
//void DXEngine::RenderMenuText() {
//}
//
//void DXEngine::EndSprite() {
//}
//void DXEngine::EndScene() {
//}


/*
 * Releases dynamically allocated memory only when necessary to prevent runtime errors
 */
void DXEngine::Release()
{
	GetLastError();
	if(m_pD3DDevice != NULL)			// release device and set to null
	{
		m_pD3DDevice->Release();
		m_pD3DDevice = NULL;
	}
	if(m_pFontManager != NULL)			// release font manager and set to null
	{
		m_pFontManager->Release();
		m_pFontManager = NULL;
	}
	if(m_pSpriteManager != NULL)		// release sprite manager and set to null
	{
		m_pSpriteManager->Release();
		m_pSpriteManager = NULL;
	}
	//delete [] DrawList;
}

DXEngine::~DXEngine()
{
	DXEngine::Release();
}